//go:build darwin || dragonfly || freebsd || netbsd || openbsd

package epoll

import (
	"errors"
	"sync"

	"golang.org/x/sys/unix"

	"gitee.com/hasika/common-server/conn/inet"
)

type Epoll struct {
	efd         int
	connections map[int]inet.Conn
	lock        *sync.RWMutex
}

func MkEpoll() (*Epoll, error) {
	fd, err := unix.Kqueue()
	if err != nil {
		return nil, err
	}
	return &Epoll{
		efd:         fd,
		lock:        &sync.RWMutex{},
		connections: make(map[int]inet.Conn),
	}, nil
}

func (e *Epoll) Add(conn inet.Conn) error {
	fd := conn.GetFd()
	e.lock.Lock()
	defer e.lock.Unlock()
	e.connections[fd] = conn
	ev := e.createKEvent(fd, true)
	n, err := unix.Kevent(e.efd, []unix.Kevent_t{ev}, nil, &unix.Timespec{Sec: 10})
	if err != nil || n < 0 {
		Log.Errorf("kevent add error %+v", err)
		return err
	}

	if len(e.connections)%100 == 0 {
		Log.Infof("total number of connections: %v", len(e.connections))
	}
	return nil
}

func (e *Epoll) Remove(conn inet.Conn) error {
	fd := conn.GetFd()
	e.lock.Lock()
	defer e.lock.Unlock()
	_, ex := e.connections[fd]
	if !ex {
		return nil
	}
	delete(e.connections, fd)
	ev := e.createKEvent(fd, false)
	n, err := unix.Kevent(e.efd, []unix.Kevent_t{ev}, nil, nil)
	if err != nil || n < 0 {
		Log.Errorf("kevent remove error %+v", err)
		return err
	}
	return nil
}

func (e *Epoll) Wait() ([]inet.Conn, error) {
	events := make([]unix.Kevent_t, 1000)
	n, err := unix.Kevent(e.efd, nil, events, nil)
	if err != nil && !errors.Is(err, unix.EINTR) {
		Log.Errorf("kevent error %+v", err)
		return nil, nil
	}
	e.lock.RLock()
	defer e.lock.RUnlock()
	var connections []inet.Conn
	for i := 0; i < n; i++ {
		conn := e.connections[int(events[i].Ident)]
		connections = append(connections, conn)
	}
	return connections, nil
}

func (e *Epoll) createKEvent(fd int, add bool) unix.Kevent_t {
	flag := uint16(unix.EV_ADD | unix.EV_ENABLE | unix.EV_CLEAR | unix.EV_ONESHOT)
	if !add {
		flag = uint16(unix.EV_DELETE | unix.EV_DISABLE)
	}
	changeEvent := unix.Kevent_t{
		Ident:  uint64(fd),
		Filter: unix.EVFILT_READ,
		Flags:  flag,
		Fflags: 0,
		Data:   0,
		Udata:  nil,
	}
	return changeEvent
}
